﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;

namespace Jack.EntityFrameworkCore
{
    public class EfCoreRepository<TDbContext, TModel> : IEfCoreRepository<TModel>, IRepository<TModel>, IQueryable<TModel>, IEnumerable<TModel>, IEnumerable, IQueryable, IRepository, IAsyncEnumerable<TModel>
        where TDbContext : JackDbContext<TDbContext>
        where TModel : class, IBaseModel, new()
    {
        private readonly TDbContext _context;
        private readonly Lazy<DbSet<TModel>> _lazyDbSet;

        public EfCoreRepository(TDbContext context)
        {
            _context = context;
            _lazyDbSet = new Lazy<DbSet<TModel>>(() => _context.Set<TModel>(), true);
        }

        public virtual DbContext DbContext => _context;

        public virtual DbSet<TModel> DbSet => _lazyDbSet.Value;

        public virtual IQueryable<TModel> DbSetTracking => _lazyDbSet.Value.AsTracking();

        public virtual IQueryable<TModel> DbSetNoTracking => _lazyDbSet.Value.AsNoTracking();

        public virtual Type ElementType => GetQueryable().ElementType;

        public virtual Expression Expression => GetQueryable().Expression;

        public virtual IQueryProvider Provider => GetQueryable().Provider;

        public virtual void Delete(TModel model)
        {
            if (model == null) return;

            DbSet.Remove(model);
        }

        public virtual void Delete<TKey>(params TKey[] ids)
        {
            if (ids.Length == 0) return;
            TModel[] models = new TModel[ids.Length];
            PropertyInfo property = typeof(TModel).GetProperty(nameof(BaseModel<TKey>.Id));

            for (int i = 0; i < ids.Length; i++)
            {
                TKey key = ids[i];
                TModel model = new TModel();
                property.SetValue(model, key);
                models[i] = model;
            }

            DbSet.RemoveRange(models);
        }

        public virtual void Delete(Expression<Func<TModel, bool>> predicate)
        {
            DbSet.RemoveRange(DbSet.Where(predicate));
        }

        public virtual void DeleteMany(IEnumerable<TModel> models)
        {
            if (models == null || models.Any() == false) return;

            DbSet.RemoveRange(models);
        }

        public virtual Task<TModel> Get(Expression<Func<TModel, bool>> expression, CancellationToken cancellationToken = default)
        {
            return DbSet.FirstOrDefaultAsync(expression, cancellationToken);
        }

        public virtual IAsyncEnumerator<TModel> GetAsyncEnumerator(CancellationToken cancellationToken = default)
        {
            return DbSet.AsAsyncEnumerable().GetAsyncEnumerator(cancellationToken);
        }

        public virtual IEnumerator<TModel> GetEnumerator()
        {
            return DbSet.AsQueryable().GetEnumerator();
        }

        public virtual Task<List<TModel>> GetList(CancellationToken cancellationToken = default)
        {
            return DbSet.ToListAsync(cancellationToken);
        }

        public virtual Task<List<TModel>> GetList(Expression<Func<TModel, bool>> expression, CancellationToken cancellationToken = default)
        {
            return DbSet.Where(expression).ToListAsync(cancellationToken);
        }

        public virtual Task<List<TModel>> GetPageList(Expression<Func<TModel, bool>> expression, int skipCount, int maxResultCount, string sorting, CancellationToken cancellationToken = default)
        {
            return DynamicQueryableExtensions.OrderBy(DbSet.Where(expression), sorting).Skip(skipCount).Take(maxResultCount).ToListAsync(cancellationToken);
        }

        public virtual Task<List<TModel>> GetPageList(int skipCount, int maxResultCount, string sorting, CancellationToken cancellationToken = default)
        {
            return DynamicQueryableExtensions.OrderBy(DbSet, sorting).Skip(skipCount).Take(maxResultCount).ToListAsync(cancellationToken);
        }

        public virtual async Task<TModel> Insert(TModel model, CancellationToken cancellationToken = default)
        {
            if (model == null) return await Task.FromResult(model);

            return (await DbSet.AddAsync(model, cancellationToken)).Entity;
        }

        public virtual Task InsertMany(IEnumerable<TModel> models, CancellationToken cancellationToken = default)
        {
            if (models == null || models.Any() == false) return Task.CompletedTask;

            return DbSet.AddRangeAsync(models, cancellationToken);
        }

        public virtual TModel Update(TModel model)
        {
            if (model == null) return model;
            AttachIfNot(model);
            return DbSet.Update(model).Entity;
        }

        public virtual void UpdatePartial(TModel model, params Expression<Func<TModel, object>>[] properties)
        {
            if (model == null || properties.Length == 0) return;

            var entity = _context.Entry(model);
            foreach (Expression<Func<TModel, object>> property in properties)
            {
                entity.Property(property).IsModified = true;
            }
        }

        public virtual void UpdatePartial(TModel model, params string[] properties)
        {
            if (model == null || properties.Length == 0) return;

            var entity = _context.Entry(model);
            foreach (string property in properties)
            {
                entity.Property(property).IsModified = true;
            }
        }

        public virtual void UpdatePartialByExclude(TModel model, params string[] excludeProperties)
        {
            if (model == null) return;

            var entry = _context.Entry(model);
            foreach (var property in model.GetType().GetProperties().Where(x => !excludeProperties.Contains(x.Name)))
            {
                entry.Property(property.Name).IsModified = true;
            }
        }

        public virtual void UpdateMany(IEnumerable<TModel> models)
        {
            if (models == null || models.Any() == false) return;

            foreach (var model in models)
            {
                AttachIfNot(model);
            }
            DbSet.UpdateRange(models);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetQueryable().GetEnumerator();
        }

        protected IQueryable<TModel> GetQueryable()
        {
            return DbSet.AsQueryable();
        }

        protected virtual void AttachIfNot(TModel model)
        {
            if (model == null) return;
            EntityEntry entry = _context.ChangeTracker.Entries().FirstOrDefault((EntityEntry ent) => ent.Entity == model);
            if (entry == null)
            {
                _context.Attach(model);
            }
        }

#if DEBUG
        public void Dispose()
        {
            Console.WriteLine("释放仓储");
        }
#endif
    }
}
